package com.cocotingo.snail.template.previewer;

import java.awt.Color;
import java.awt.Font;
import java.awt.GraphicsConfiguration;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

import com.cocotingo.snail.Image;
import com.cocotingo.snail.ResizableImage;
import com.cocotingo.snail.View;
import com.cocotingo.snail.template.EventHandler;
import com.cocotingo.snail.template.TemplateParserException;
import com.cocotingo.snail.template.TemplateRuntimeException;
import com.cocotingo.snail.template.TemplateSemanticException;
import com.cocotingo.snail.template.TemplateSyntaxException;
import com.cocotingo.snail.template.TemplateView;
import com.cocotingo.snail.template.annotations.Handler;
import com.cocotingo.snail.template.annotations.Outlet;
import com.cocotingo.snail.template.models.Attribute;
import com.cocotingo.snail.template.models.HandlerNode;
import com.cocotingo.snail.template.models.LayoutFactorNode;
import com.cocotingo.snail.template.models.LayoutFactorNode.AbsoluteLayoutFactorNode;
import com.cocotingo.snail.template.models.LayoutFactorNode.FittingLayoutFactorNode;
import com.cocotingo.snail.template.models.LayoutFactorNode.NeighborLayoutFactorNode;
import com.cocotingo.snail.template.models.LayoutFactorNode.ReferencedLayoutFactorNode;
import com.cocotingo.snail.template.models.LayoutFactorPair;
import com.cocotingo.snail.template.models.PaintNode;
import com.cocotingo.snail.template.models.TemplateDefinitionsNode;
import com.cocotingo.snail.template.models.TemplateNode;
import com.cocotingo.snail.template.models.VariableNode;
import com.cocotingo.snail.template.models.VariablesNode;
import com.cocotingo.snail.template.models.ViewNode;
import com.cocotingo.snail.template.parser.AttributeParser;
import com.cocotingo.snail.template.parser.AttributeParsers;
import com.cocotingo.snail.template.parser.XMLTemplateParser;

public class TemplatePreviewer {

	private static class LoaderContext {
		
		final Map<String, TemplateNode> templates;
		final Map<TemplateNode, List<LayoutFactorPair>> sortedLayoutFactorPairs;
		
		LoaderContext() {
			templates = new HashMap<String, TemplateNode>();
			sortedLayoutFactorPairs = new HashMap<TemplateNode, List<LayoutFactorPair>>();
		}
		
	}
	
	private static interface RuntimeValueGetter {
		int getValueType();
		Object getValue();
	}
	
	private abstract static class IntegerRuntimeValueGetter implements RuntimeValueGetter {
		public int getValueType() {
			return VariableNode.INTEGER;
		}
	}
	
	public static final String VERSION = "2.0";
	private static final Map<Class<?>, AttributeParser> attributeParsers;
	
	static {
		attributeParsers = new HashMap<Class<?>, AttributeParser>();
		Set<AttributeParser> parsers = AttributeParsers.getDefaultParsers();
		for (AttributeParser parser : parsers) {
			registerAttributeParser(parser);
		}
	}
	
	private static AttributeParser registerAttributeParser(AttributeParser parser) {
		return attributeParsers.put(parser.getResultType(), parser);
	}

	public static boolean canParseAttributeType(Class<?> clazz) {
		return attributeParsers.containsKey(clazz);
	}
	
	public static AttributeParser getAttributeParser(Class<?> clazz) {
		return attributeParsers.get(clazz);
	}
	
	//// Instance Methods ////

	private abstract class LayoutEventHandler implements EventHandler {
		private final RuntimeValueGetter getter1, getter2;
		LayoutEventHandler(RuntimeValueGetter getter1, RuntimeValueGetter getter2) {
			this.getter1 = getter1;
			this.getter2 = getter2;
		}
		@Override
		public void handle(TemplateView sender, String eventName, Object arg) {
			handle((Integer) getter1.getValue(), (Integer) getter2.getValue());
		}
		abstract protected void handle(int v1, int v2);
	}

	private XMLTemplateParser parser;
	private final GraphicsConfiguration configuration;
	private final Map<String, Object> resources;
	private final List<LoaderContext> contexts;

	public TemplatePreviewer(GraphicsConfiguration gc) {
		parser = null;
		if (gc == null) throw new IllegalArgumentException("GraphicsConfiguration shouldn't be null");
		configuration = gc;
		resources = new HashMap<String, Object>();
		contexts = new ArrayList<LoaderContext>();
	}
	
	public final GraphicsConfiguration getConfiguration() {
		return configuration;
	}
	
	public final Image getResourceAsImage(String id) {
		return (Image) resources.get(id);
	}
	
	public final ResizableImage getResourceAsResizableImage(String id) {
		return (ResizableImage) resources.get(id);
	}
	
	public final Color getResourceAsColor(String id) {
		return (Color) resources.get(id);
	}
	
	public final Font getResourceAsFont(String id) {
		return (Font) resources.get(id);
	}
	
	public final Class<?> getResourceAsClass(String id) {
		return (Class<?>) resources.get(id);
	}
	
	public void load(InputStream xmlInputStream) throws TemplateParserException, TemplateSyntaxException, TemplateSemanticException {
		if (xmlInputStream == null) throw new IllegalArgumentException("XML resource name shouldn't be null");
		if (parser == null) {
			parser = new XMLTemplateParser();
		}
		TemplateDefinitionsNode node = parser.parse(xmlInputStream);
		node.validate();
		if (!node.getVersion().equals(VERSION)) {
			throw new TemplateSemanticException("incorrect template version: " + node.getVersion());
		}
		LoaderContext context = new LoaderContext();
		contexts.add(context);
		loadTemplates(context, node);
	}
	
	private void loadTemplates(LoaderContext context, TemplateDefinitionsNode rootNode) throws TemplateSemanticException {
		for (TemplateNode node : rootNode.templateNodes()) {
			if (getContext(node.getID()) == null) {
				context.templates.put(node.getID(), node);
				try {
					context.sortedLayoutFactorPairs.put(node, node.createSortedLayoutFactorPairs());
				} catch (TemplateSyntaxException e) {
					throw new TemplateSemanticException("incorrect layout factors", e);
				}
			} else {
				throw new TemplateSemanticException("re-defined template: " + node.getID());
			}
		}
	}
	
	private LoaderContext getContext(String templateID) {
		for (LoaderContext c : contexts) {
			if (c.templates.containsKey(templateID)) return c;
		}
		return null;
	}
	
	//// Instance Methods ////
	
	private class MakerContext {
		
		final LoaderContext loaderContext;
		final String templateID;
		TemplatePreviewerView rootView;
		final List<TemplatePreviewerView> allViews;
		final Map<String, TemplatePreviewerView> viewMap;
		final Map<String, RuntimeValueGetter> varMap;
		
		MakerContext(LoaderContext loaderContext, String templateID) {
			this.templateID = templateID;
			this.loaderContext = loaderContext;
			this.rootView = null;
			allViews = new LinkedList<TemplatePreviewerView>(); // records all the views created
			viewMap = new HashMap<String, TemplatePreviewerView>(); // records the views with id
			varMap = new HashMap<String, RuntimeValueGetter>();
		}
		
		boolean hasSameID(String id) {
			return varMap.containsKey(id) || viewMap.containsKey(id);
		}
		
	}

	public TemplatePreviewerView makeView(String templateID) throws TemplateRuntimeException {
		LoaderContext loaderContext = getContext(templateID);
		if (loaderContext == null) throw new TemplateRuntimeException("can not find template named: " + templateID);
		TemplateNode template = loaderContext.templates.get(templateID);
		MakerContext context = new MakerContext(loaderContext, templateID);
		
		// create view structure
		ViewNode rootViewNode = template.getRootViewNode();
		context.rootView = createView(rootViewNode, context, null);
		
		// binding template variables
		VariablesNode varsNode = template.getVariablesNode();
		if (varsNode != null) {
			for (VariableNode<?> varNode : varsNode.variableNodes()) {
				createDefaultVariableGetter(varNode, context);
			}
		}
		
		// setting view
		buildView(rootViewNode, context.allViews.iterator(), context);
		
		// layout
		buildAutoLayoutHandlers(context);
		
		return context.rootView;
	}
	
	public String[] getAllTemplateIDs() {
		ArrayList<String> ids = new ArrayList<String>();
		for (LoaderContext cxt : contexts) {
			ids.addAll(cxt.templates.keySet());
		}
		return ids.toArray(new String[0]);
	}
	
	private void createDefaultVariableGetter(VariableNode<?> varNode, MakerContext context) throws TemplateRuntimeException {
		String id = varNode.getID();
		if (id != null && !id.isEmpty()) {
			if (!context.hasSameID(id)) {
				final int type = varNode.getType();
				final Object default_value = varNode.getDefaultValue();
				context.varMap.put(id, new RuntimeValueGetter() {
					@Override
					public int getValueType() {
						return type;
					}
					@Override
					public Object getValue() {
						return default_value;
					}
				});
			} else {
				throw new TemplateRuntimeException("re-defined id: " + id);
			}
		}
	}
	
	private TemplatePreviewerView createView(ViewNode node, MakerContext context, TemplatePreviewerView rootView) throws TemplateRuntimeException {
		TemplatePreviewerView view = new TemplatePreviewerView(rootView);
		if (rootView == null) rootView = view;
		
		String className = node.getClassName();
		String templateName = node.getTemplateName();
		if (className != null && !className.isEmpty()) {
			view.setTypeName(className);
		} else if (templateName != null && !templateName.isEmpty()) {
			view.setTypeName(templateName);
		}
		
		// record view and id
		context.allViews.add(view);
		
		String id = node.getID();
		view.setID(id);
		if (id != null && !id.isEmpty()) {
			if (!context.hasSameID(id)) {
				context.viewMap.put(id, view);
			} else {
				throw new TemplateRuntimeException("re-defined id: " + id);
			}
		}
		
		// create subviews
		for (ViewNode viewNode : node.viewNodes()) {
			TemplateView subview = createView(viewNode, context, rootView);
			view.addSubview(subview);
		}
		
		return view;
	}
	
	private void buildView(ViewNode node, Iterator<TemplatePreviewerView> it, MakerContext context) throws TemplateRuntimeException {
		final TemplatePreviewerView view = it.next();
		
		// set view attributes
		for (Attribute attr : node.attributes()) {
			view.addAttribute(attr.getName(), attr.getValue());
		}
		
		// handlers
		HandlerNode handlerNode = node.getHandlerNode();
		if (handlerNode != null) {
			for (String event : handlerNode.eventNames()) {
				String handler = handlerNode.getHandlerName(event);
				view.addHandlerBinding(event, handler);
			}
		}
		
		// paint
		PaintNode paintNode = node.getPaintNode();
		if (paintNode != null) {
//			for (PaintFactorNode factorNode : paintNode.factors()) {
//				switch (factorNode.getType()) {
//				case PaintFactorNode.IMAGE:
//					buildPaintImage((PaintFactorNode.PaintImageNode) factorNode, view);
//					break;
//
//				default:
//					throw new TemplateRuntimeException("nnknown paint factor type: " + factorNode.getType());
//				}
//			}
		}
		
		for (ViewNode n : node.viewNodes()) {
			buildView(n, it, context);
		}
	}
	
	//// Layout ////
	
	private void buildAutoLayoutHandlers(MakerContext context) throws TemplateRuntimeException {
		TemplateNode template = context.loaderContext.templates.get(context.templateID);
		List<LayoutFactorPair> pairs = context.loaderContext.sortedLayoutFactorPairs.get(template);
		for (LayoutFactorPair pair : pairs) {
			TemplateView targetView = context.allViews.get(pair.getViewIndex());
			LayoutFactorNode factor1 = pair.getFactor1();
			LayoutFactorNode factor2 = pair.getFactor2();
			if (factor1.getFactorType() == factor2.getFactorType()) {
				throw new TemplateRuntimeException("duplicate layout factors in one layout node: " + factor1.getFactorType());
			}
			EventHandler handler = null;
			if (pair.isHorizentalPair()) {
				if (factor1.getFactorType() > factor2.getFactorType()) {
					handler = buildHorizentalLayoutHandler(targetView, factor2, factor1, context);
				} else {
					handler = buildHorizentalLayoutHandler(targetView, factor1, factor2, context);
				}
			} else {
				if (factor1.getFactorType() > factor2.getFactorType()) {
					handler = buildVerticalLayoutHandler(targetView, factor2, factor1, context);
				} else {
					handler = buildVerticalLayoutHandler(targetView, factor1, factor2, context);
				}
			}
			context.rootView.addEventHandler(TemplateView.AUTO_LAYOUT, handler);
		}
	}
	
	private EventHandler buildHorizentalLayoutHandler(final TemplateView targetView, LayoutFactorNode factor1, LayoutFactorNode factor2, MakerContext context) throws TemplateRuntimeException {
		RuntimeValueGetter getter1 = getRuntimeValueGetterFromLayoutFactor(targetView, factor1, context);
		RuntimeValueGetter getter2 = getRuntimeValueGetterFromLayoutFactor(targetView, factor2, context);
		int t1 = factor1.getFactorType();
		int t2 = factor2.getFactorType();
		if (t1 == LayoutFactorNode.LEFT && t2 == LayoutFactorNode.CENTER_LEFT) {
			return new LayoutEventHandler(getter1, getter2) {
				@Override
				protected void handle(int v1, int v2) {
					targetView.setLeft(v1);
					targetView.setWidth(2 * (v2 - v1));
				}
			};
		} else if (t1 == LayoutFactorNode.LEFT && t2 == LayoutFactorNode.RIGHT) {
			return new LayoutEventHandler(getter1, getter2) {
				@Override
				protected void handle(int v1, int v2) {
					View.scaleViewWithLeftAndRight(targetView, v1, v2);
				}
			};
		} else if (t1 == LayoutFactorNode.LEFT && t2 == LayoutFactorNode.WIDTH) {
			return new LayoutEventHandler(getter1, getter2) {
				@Override
				protected void handle(int v1, int v2) {
					targetView.setLeft(v1);
					targetView.setWidth(v2);
				}
			};
		} else if (t1 == LayoutFactorNode.CENTER_LEFT && t2 == LayoutFactorNode.RIGHT) {
			return new LayoutEventHandler(getter1, getter2) {
				@Override
				protected void handle(int v1, int v2) {
					int width = 2 * (targetView.getSuperView().getWidth() - v1 - v2);
					targetView.setLeft(v1 - width / 2);
					targetView.setWidth(width);
				}
			};
		} else if (t1 == LayoutFactorNode.CENTER_LEFT && t2 == LayoutFactorNode.WIDTH) {
			return new LayoutEventHandler(getter1, getter2) {
				@Override
				protected void handle(int v1, int v2) {
					targetView.setLeft(v1 - v2 / 2);
					targetView.setWidth(v2);
				}
			};
		} else if (t1 == LayoutFactorNode.RIGHT && t2 == LayoutFactorNode.WIDTH) {
			return new LayoutEventHandler(getter1, getter2) {
				@Override
				protected void handle(int v1, int v2) {
					targetView.setWidth(v2);
					View.putViewWithRight(targetView, v1);
				}
			};
		} else {
			throw new TemplateRuntimeException();
		}
	}
	
	private EventHandler buildVerticalLayoutHandler(final TemplateView targetView, LayoutFactorNode factor1, LayoutFactorNode factor2, MakerContext context) throws TemplateRuntimeException {
		RuntimeValueGetter getter1 = getRuntimeValueGetterFromLayoutFactor(targetView, factor1, context);
		RuntimeValueGetter getter2 = getRuntimeValueGetterFromLayoutFactor(targetView, factor2, context);
		int t1 = factor1.getFactorType();
		int t2 = factor2.getFactorType();
		if (t1 == LayoutFactorNode.TOP && t2 == LayoutFactorNode.CENTER_TOP) {
			return new LayoutEventHandler(getter1, getter2) {
				@Override
				protected void handle(int v1, int v2) {
					targetView.setTop(v1);
					targetView.setHeight(2 * (v2 - v1));
				}
			};
		} else if (t1 == LayoutFactorNode.TOP && t2 == LayoutFactorNode.BOTTOM) {
			return new LayoutEventHandler(getter1, getter2) {
				@Override
				protected void handle(int v1, int v2) {
					View.scaleViewWithTopAndBottom(targetView, v1, v2);
				}
			};
		} else if (t1 == LayoutFactorNode.TOP && t2 == LayoutFactorNode.HEIGHT) {
			return new LayoutEventHandler(getter1, getter2) {
				@Override
				protected void handle(int v1, int v2) {
					targetView.setTop(v1);
					targetView.setHeight(v2);
				}
			};
		} else if (t1 == LayoutFactorNode.CENTER_TOP && t2 == LayoutFactorNode.BOTTOM) {
			return new LayoutEventHandler(getter1, getter2) {
				@Override
				protected void handle(int v1, int v2) {
					int height = 2 * (targetView.getSuperView().getHeight() - v1 - v2);
					targetView.setTop(v1 - height / 2);
					targetView.setHeight(height);
				}
			};
		} else if (t1 == LayoutFactorNode.CENTER_TOP && t2 == LayoutFactorNode.HEIGHT) {
			return new LayoutEventHandler(getter1, getter2) {
				@Override
				protected void handle(int v1, int v2) {
					targetView.setTop(v1 - v2 / 2);
					targetView.setHeight(v2);
				}
			};
		} else if (t1 == LayoutFactorNode.BOTTOM && t2 == LayoutFactorNode.HEIGHT) {
			return new LayoutEventHandler(getter1, getter2) {
				@Override
				protected void handle(int v1, int v2) {
					targetView.setHeight(v2);
					View.putViewWithBottom(targetView, v1);
				}
			};
		} else {
			throw new TemplateRuntimeException();
		}
	}
	
	private RuntimeValueGetter getRuntimeValueGetterFromLayoutFactor(final TemplateView targetView, LayoutFactorNode factor, MakerContext context) throws TemplateRuntimeException {
		final RuntimeValueGetter intGetter = getIntegerValueGetterFromLayoutFactor(factor, context);
		final RuntimeValueGetter doubleGetter = getDoubleValueGetterFromLayoutFactor(factor, context);
		final TemplateView referencedView = getViewArgumentFromLayoutFactor(factor, context);
		switch (factor.getLayoutType()) {
		case LayoutFactorNode.LAYOUT_ABSOLUTE:
			return intGetter;
		case LayoutFactorNode.LAYOUT_REFERENCED:
			switch (factor.getFactorType()) {
			case LayoutFactorNode.LEFT:
				return new IntegerRuntimeValueGetter() {
					@Override
					public Object getValue() {
						return (int) (referencedView.getLeft() * (Double) doubleGetter.getValue());
					}
				};
			case LayoutFactorNode.RIGHT:
				return new IntegerRuntimeValueGetter() {
					@Override
					public Object getValue() {
						return (int) (referencedView.getRight() * (Double) doubleGetter.getValue());
					}
				};
			case LayoutFactorNode.CENTER_LEFT:
				return new IntegerRuntimeValueGetter() {
					@Override
					public Object getValue() {
						return (int) ((referencedView.getLeft() + referencedView.getWidth() / 2.0) * (Double) doubleGetter.getValue());
					}
				};
			case LayoutFactorNode.WIDTH:
				return new IntegerRuntimeValueGetter() {
					@Override
					public Object getValue() {
						return (int) (referencedView.getWidth() * (Double) doubleGetter.getValue());
					}
				};
			case LayoutFactorNode.TOP:
				return new IntegerRuntimeValueGetter() {
					@Override
					public Object getValue() {
						return (int) (referencedView.getTop() * (Double) doubleGetter.getValue());
					}
				};
			case LayoutFactorNode.BOTTOM:
				return new IntegerRuntimeValueGetter() {
					@Override
					public Object getValue() {
						return (int) (referencedView.getBottom() * (Double) doubleGetter.getValue());
					}
				};
			case LayoutFactorNode.CENTER_TOP:
				return new IntegerRuntimeValueGetter() {
					@Override
					public Object getValue() {
						return (int) ((referencedView.getTop() + referencedView.getHeight() / 2.0) * (Double) doubleGetter.getValue());
					}
				};
			case LayoutFactorNode.HEIGHT:
				return new IntegerRuntimeValueGetter() {
					@Override
					public Object getValue() {
						return (int) (referencedView.getHeight() * (Double) doubleGetter.getValue());
					}
				};
			default:
				throw new TemplateRuntimeException();
			}
		case LayoutFactorNode.LAYOUT_NEIGHBOR:
			switch (factor.getFactorType()) {
			case LayoutFactorNode.LEFT:
				return new IntegerRuntimeValueGetter() {
					@Override
					public Object getValue() {
						return referencedView.getLeft() + referencedView.getWidth() + (Integer) intGetter.getValue();
					}
				};
			case LayoutFactorNode.TOP:
				return new IntegerRuntimeValueGetter() {
					@Override
					public Object getValue() {
						return referencedView.getTop() + referencedView.getHeight() + (Integer) intGetter.getValue();
					}
				};
			case LayoutFactorNode.RIGHT:
				return new IntegerRuntimeValueGetter() {
					@Override
					public Object getValue() {
						return referencedView.getRight() + referencedView.getWidth() + (Integer) intGetter.getValue();
					}
				};
			case LayoutFactorNode.BOTTOM:
				return new IntegerRuntimeValueGetter() {
					@Override
					public Object getValue() {
						return referencedView.getBottom() + referencedView.getHeight() + (Integer) intGetter.getValue();
					}
				};
			default:
				throw new TemplateRuntimeException();
			}
		case LayoutFactorNode.LAYOUT_FITTING:
			switch (factor.getFactorType()) {
			case LayoutFactorNode.WIDTH:
				return new IntegerRuntimeValueGetter() {
					@Override
					public Object getValue() {
						int width = targetView.getWidth();
						width -= referencedView.getRight() - (Integer) intGetter.getValue();
						return width;
					}
				};
			case LayoutFactorNode.HEIGHT:
				return new IntegerRuntimeValueGetter() {
					@Override
					public Object getValue() {
						int height = targetView.getHeight();
						height -= referencedView.getBottom() - (Integer) intGetter.getValue();
						return height;
					}
				};
			default:
				throw new TemplateRuntimeException();
			}
		case LayoutFactorNode.LAYOUT_PREFERRED:
			switch (factor.getFactorType()) {
			case LayoutFactorNode.WIDTH:
				return new IntegerRuntimeValueGetter() {
					@Override
					public Object getValue() {
						return targetView.getPreferredWidth();
					}
				};
			case LayoutFactorNode.HEIGHT:
				return new IntegerRuntimeValueGetter() {
					@Override
					public Object getValue() {
						return targetView.getPreferredHeight();
					}
				};
			default:
				throw new TemplateRuntimeException();
			}
		case LayoutFactorNode.LAYOUT_CENTERED:
			switch (factor.getFactorType()) {
			case LayoutFactorNode.CENTER_LEFT:
				return new IntegerRuntimeValueGetter() {
					@Override
					public Object getValue() {
						return targetView.getSuperView().getWidth() / 2;
					}
				};
			case LayoutFactorNode.CENTER_TOP:
				return new IntegerRuntimeValueGetter() {
					@Override
					public Object getValue() {
						return targetView.getSuperView().getHeight() / 2;
					}
				};
			default:
				throw new TemplateRuntimeException();
			}
		default:
			throw new TemplateRuntimeException();
		}
	}
	
	private RuntimeValueGetter getIntegerValueGetterFromLayoutFactor(LayoutFactorNode factor, MakerContext context) throws TemplateRuntimeException {
		String value = null;
		switch (factor.getLayoutType()) {
		case LayoutFactorNode.LAYOUT_ABSOLUTE:
			value = ((AbsoluteLayoutFactorNode) factor).getValue();
			break;
		case LayoutFactorNode.LAYOUT_NEIGHBOR:
			value = ((NeighborLayoutFactorNode) factor).getMargin();
			break;
		case LayoutFactorNode.LAYOUT_FITTING:
			value = ((FittingLayoutFactorNode) factor).getMargin();
			break;
		default:
			return null;
		}
		return getIntegerValueGetter(value, context);
	}
	
	private RuntimeValueGetter getDoubleValueGetterFromLayoutFactor(LayoutFactorNode factor, MakerContext context) throws TemplateRuntimeException {
		String value = null;
		switch (factor.getLayoutType()) {
		case LayoutFactorNode.LAYOUT_REFERENCED:
			value = ((ReferencedLayoutFactorNode) factor).getMultiplier();
			break;
		default:
			return null;
		}
		return getDoubleValueGetter(value, context);
	}
	
	private TemplateView getViewArgumentFromLayoutFactor(LayoutFactorNode factor, MakerContext context) throws TemplateRuntimeException {
		String id = null;
		switch (factor.getLayoutType()) {
		case LayoutFactorNode.LAYOUT_REFERENCED:
			id = ((ReferencedLayoutFactorNode) factor).getReferencedView();
			break;
		case LayoutFactorNode.LAYOUT_NEIGHBOR:
			id = ((NeighborLayoutFactorNode) factor).getNeighborView();
			break;
		case LayoutFactorNode.LAYOUT_FITTING:
			id = ((FittingLayoutFactorNode) factor).getContentView();
			break;
		default:
			return null;
		}
		TemplateView view = context.viewMap.get(id);
		if (view == null) throw new TemplateRuntimeException("can not find view named: " + id);
		return view;
	}
	
	private RuntimeValueGetter getIntegerValueGetter(String value, MakerContext context) throws TemplateRuntimeException {
		if (value == null) value = "0";
		try {
			final Integer result = (Integer) attributeParsers.get(int.class).parse(null, value);
			return new RuntimeValueGetter() {
				@Override
				public int getValueType() {
					return VariableNode.INTEGER;
				}
				@Override
				public Object getValue() {
					return result;
				}
			};
		} catch (TemplateRuntimeException e) {
			RuntimeValueGetter getter = context.varMap.get(value);
			if (getter == null || getter.getValueType() != VariableNode.INTEGER) throw new TemplateRuntimeException("can not find integer variable named: " + value);
			return getter;
		}
	}
	
	private RuntimeValueGetter getDoubleValueGetter(String value, MakerContext context) throws TemplateRuntimeException {
		if (value == null) value = "1.0";
		try {
			final Double result = (Double) attributeParsers.get(double.class).parse(null, value);
			return new RuntimeValueGetter() {
				@Override
				public int getValueType() {
					return VariableNode.DOUBLE;
				}
				@Override
				public Object getValue() {
					return result;
				}
			};
		} catch (TemplateRuntimeException e) {
			RuntimeValueGetter getter = context.varMap.get(value);
			if (getter == null || getter.getValueType() != VariableNode.DOUBLE) throw new TemplateRuntimeException("can not find double variable named: " + value);
			return getter;
		}
	}
	
}

class ReflectionCache {
	
	private static ReflectionCache instance;
	
	static ReflectionCache getInstance() {
		if (instance == null) instance = new ReflectionCache();
		return instance;
	}
	
	private static class ClassInfo {
		
		Class<?> clazz;
		Method[] methods;
		final Map<String, Method> setters;
		List<Field> template_vars;
		List<Method> template_callbacks;
		
		ClassInfo(Class<?> clazz) {
			this.clazz = clazz;
			this.methods = null;
			this.setters = new HashMap<String, Method>();
			this.template_vars = null;
			this.template_callbacks = null;
		}
		
		Method findSetter(String setterName) {
			if (!setters.containsKey(setterName)) {
				if (methods == null) {
					methods = clazz.getMethods();
				}
				Method setter = null;
				for (Method method : methods) {
					if (method.getName().equals(setterName)) {
						Class<?>[] paraClass = method.getParameterTypes();
						if (paraClass.length == 1) {
							setter = method;
							break;
						}
					}
				}
				setters.put(setterName, setter);
			}
			return setters.get(setterName);
		}
		
		Iterable<Field> getAllTemplateOutlets() {
			if (template_vars == null) {
				template_vars = new ArrayList<Field>();
				Field[] fields = clazz.getFields();
				for (Field field : fields) {
					if (field.isAnnotationPresent(Outlet.class)) {
						template_vars.add(field);
					}
				}
			}
			return template_vars;
		}
		
		Iterable<Method> getAllTemplateHandlers() {
			if (template_callbacks == null) {
				template_callbacks = new ArrayList<Method>();
				Method[] methods = clazz.getMethods();
				for (Method method : methods) {
					if (method.isAnnotationPresent(Handler.class)) {
						template_callbacks.add(method);
					}
				}
			}
			return template_callbacks;
		}
		
	}
	
	private final Map<String, String> setterNamesCache;
	private final Map<Class<?>, ClassInfo> classInfos;
	
	private ReflectionCache() {
		setterNamesCache = new HashMap<String, String>();
		classInfos = new HashMap<Class<?>, ClassInfo>();
	}
	
	Method findSetter(Class<?> clazz, String attributeName) {
		if (!setterNamesCache.containsKey(attributeName)) {
			StringBuilder buf = new StringBuilder("set");
			buf.append(attributeName);
			buf.setCharAt(3, Character.toUpperCase(attributeName.charAt(0)));
			setterNamesCache.put(attributeName, buf.toString());
		}
		String setterName = setterNamesCache.get(attributeName);
		ClassInfo classInfo = classInfos.get(clazz);
		if (classInfo == null) {
			classInfo = new ClassInfo(clazz);
			classInfos.put(clazz, classInfo);
		}
		return classInfo.findSetter(setterName);
	}
	
	Iterable<Field> getAllTemplateOutlets(Class<?> clazz) {
		ClassInfo classInfo = classInfos.get(clazz);
		if (classInfo == null) {
			classInfo = new ClassInfo(clazz);
			classInfos.put(clazz, classInfo);
		}
		return classInfo.getAllTemplateOutlets();
	}
	
	Iterable<Method> getAllTemplateHandlers(Class<?> clazz) {
		ClassInfo classInfo = classInfos.get(clazz);
		if (classInfo == null) {
			classInfo = new ClassInfo(clazz);
			classInfos.put(clazz, classInfo);
		}
		return classInfo.getAllTemplateHandlers();
	}
	
}
